package com.junjiao.util.java;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.channels.FileChannel;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.tools.zip.ZipEntry;
import org.apache.tools.zip.ZipFile;
import org.apache.tools.zip.ZipOutputStream;

/**
 * 文件操作工具类
 * <p>
 * readFileLineToList 读文件行，多用于操作txt文档<br>
 * deleteFile 删除某个文件夹下的所有文件夹和文件<br>
 * readFileLineToList 读取某个文件夹下的所有文件夹和文件, 返回所有文件名<br>
 * rename 批量重命名文件名<br>
 * fileMove 移动指定文件夹内的全部文件<br>
 * mergeFiles 文件合并<br>
 * copyFile 复制单个文件(如果目标文件存在则覆盖)<br>
 * copyDirectory 复制整个目录的内容(如果目标目录存在将覆盖)<br>
 * ZIP 压缩文件或者文件夹<br>
 * UnZIP 解压缩zip文件<br>
 * </p>
 * 
 * @author jiaojun
 *         <p>
 *         email:junjiao.j@gmail.com
 *         </p>
 * @version v0.1.3
 * @date 2009-5-21
 */
public class FileUtil {
	private static final Log log = LogFactory.getLog(FileUtil.class);

	public static String FilePath;

	static {
		File f = new File(FileUtil.class.getResource("/").getPath());
		FilePath = f.getParentFile().getParentFile().getPath().replace('\\', '/').toString();
	}

	/**
	 * 读文件行，多用于操作txt文档
	 * 
	 * @param filePath
	 *            文件路径
	 * @return 每行的内容，用list保存这些字符串
	 */
	public static List<String> readFileLineToList(String filePath) {
		File file = new File(filePath);
		List<String> tempStr = new ArrayList<String>();
		try {
			if (!file.exists())
				return tempStr;
			String tempString = null;
			InputStreamReader insReader = new InputStreamReader(new FileInputStream(file), "utf-8");
			BufferedReader reader = new BufferedReader(insReader);
			while ((tempString = reader.readLine()) != null)
				tempStr.add(tempString);
			reader.close();
			reader = null;
			file = null;
		} catch (Exception e) {
			e.printStackTrace();
		}
		return tempStr;
	}

	/**
	 * 删除某个文件夹下的所有文件夹和文件
	 * 
	 * @param delpath
	 *            String
	 * @throws FileNotFoundException
	 * @throws IOException
	 * @return boolean
	 */
	public static boolean deleteFile(String delpath) throws FileNotFoundException, IOException {
		try {
			File file = new File(delpath);
			if (!file.isDirectory()) {
				file.delete();
			} else if (file.isDirectory()) {
				String[] filelist = file.list();
				for (int i = 0; i < filelist.length; i++) {
					File delfile = new File(delpath + "/" + filelist[i]);
					if (!delfile.isDirectory()) {
						log.debug("path=" + delfile.getPath());
						log.debug("absolutepath=" + delfile.getAbsolutePath());
						log.debug("name=" + delfile.getName());
						delfile.delete();
						log.debug("删除文件成功");
					} else if (delfile.isDirectory()) {
						deleteFile(delpath + "/" + filelist[i]);
					}
				}
				file.delete();
			}
		} catch (FileNotFoundException e) {
			log.error("deletefile() Exception:" + e.getMessage());
		}
		return true;
	}

	/**
	 * 
	 * 读取某个文件夹下的所有文件夹和文件, 返回所有文件名
	 * 
	 * @param filepath
	 *            String
	 * @throws FileNotFoundException
	 * @throws IOException
	 * @return Map<Integer, String> pathMap
	 * 
	 */
	public static Map<Integer, String> readFileName(String filepath, Map<Integer, String> pathMap) throws Exception {
		if (pathMap == null) {
			pathMap = new HashMap<Integer, String>();
		}
		File file = new File(filepath);
		// 文件
		if (!file.isDirectory()) {
			pathMap.put(pathMap.size(), file.getPath());
		} else if (file.isDirectory()) { // 如果是目录， 遍历所有子目录取出所有文件名
			String[] filelist = file.list();
			for (int i = 0; i < filelist.length; i++) {
				File readfile = new File(filepath + "/" + filelist[i]);
				if (!readfile.isDirectory()) {
					pathMap.put(pathMap.size(), readfile.getPath());
				} else if (readfile.isDirectory()) { // 子目录的目录
					readFileName(filepath + "/" + filelist[i], pathMap);
				}
			}
		}
		return pathMap;
	}

	/**
	 * 
	 * 读取某个文件夹下的所有文件夹和文件, 返回所有文件名
	 * 
	 * @param filepath
	 *            String
	 * @throws FileNotFoundException
	 * @throws IOException
	 * @return List<File> files
	 * 
	 */
	public static List<File> readFile(String filepath, List<File> files) throws Exception {
		if (files == null) {
			files = new ArrayList<File>();
		}
		File file = new File(filepath);
		// 文件
		if (!file.isDirectory()) {
			files.add(file);
		} else if (file.isDirectory()) { // 如果是目录， 遍历所有子目录取出所有文件名
			String[] filelist = file.list();
			for (int i = 0; i < filelist.length; i++) {
				File readfile = new File(filepath + "/" + filelist[i]);
				if (!readfile.isDirectory()) {
					files.add(readfile);
				} else if (readfile.isDirectory()) { // 子目录的目录
					readFile(filepath + "/" + filelist[i], files);
				}
			}
		}
		return files;
	}

	/**
	 * 批量重命名文件名
	 * 
	 * @param files
	 *            文件列表(文件夹或文件)
	 * @param newName
	 *            新文件名
	 * @throws Exception
	 *             可能的异常
	 */
	public static void rename(List<File> files, String newName) throws Exception {
		if (files == null) {
			return;
		}
		for (int i = 0; i < files.size(); i++) {
			if (files.get(i) != null && files.get(i).exists()) {
				File f = files.get(i);

				int lastIndex = f.getAbsolutePath().lastIndexOf(File.separator);
				// 父目录
				String path = f.getAbsolutePath().substring(0, lastIndex + 1);
				if (f.isFile()) {
					// 文件，保持后缀名
					String extensions = f.getName().lastIndexOf(".") > 0 ? (f.getName().substring(f.getName().lastIndexOf("."))) : "";
					f.renameTo(new File(path + newName.replaceAll("[.][^.]+$", "") + extensions));
				}
			}
		}
	}

	/**
	 * 移动指定文件夹内的全部文件
	 * 
	 * @param fromDir
	 *            要移动的文件目录
	 * @param toDir
	 *            目标文件目录
	 * @throws Exception
	 */
	public static void fileMove(String from, String to) throws Exception {
		try {
			File dir = new File(from);
			// 文件一览
			File[] files = dir.listFiles();
			if (files == null)
				return;
			// 目标
			File moveDir = new File(to);
			if (!moveDir.exists()) {
				moveDir.mkdirs();
			}
			// 文件移动
			for (int i = 0; i < files.length; i++) {
				if (files[i].isDirectory()) {
					fileMove(files[i].getPath(), to + "\\" + files[i].getName());
					// 成功，删除原文件
					files[i].delete();
				}
				File moveFile = new File(moveDir.getPath() + "\\" + files[i].getName());
				// 目标文件夹下存在的话，删除
				if (moveFile.exists()) {
					moveFile.delete();
				}
				files[i].renameTo(moveFile);
			}
		} catch (Exception e) {
			log.error(e.getMessage());
		}
	}

	public static final int BUFSIZE = 1024 * 1024;

	/**
	 * 文件合并
	 * 
	 * @description: <br>
	 * @author:jiaojun
	 * @param outFile
	 * @param files
	 */
	public static void mergeFiles(String outFile, String[] files) {
		Charset chrst = Charset.forName("GBK");
		FileChannel outChannel = null;
		log.info("Merge " + Arrays.toString(files) + " into " + outFile);
		try {
			outChannel = new FileOutputStream(outFile).getChannel();
			for (String f : files) {
				FileChannel fc = new FileInputStream(f).getChannel();
				ByteBuffer bb = ByteBuffer.allocate(BUFSIZE);
				while (fc.read(bb) != -1) {
					bb.flip();
					CharBuffer charReader = chrst.decode(bb);
					outChannel.write(chrst.encode(charReader));
					bb.clear();
				}
				fc.close();
			}
			log.info("Merged!! ");
		} catch (IOException ioe) {
			ioe.printStackTrace();
		} finally {
			try {
				if (outChannel != null) {
					outChannel.close();
				}
			} catch (IOException ignore) {
			}
		}
	}

	/**
	 * 复制单个文件(如果目标文件存在则覆盖)
	 * 
	 * @param srcFileName
	 *            待复制的文件名
	 * @param descFileName
	 *            目标文件名
	 * @return 如果复制成功返回true，否则返回false
	 */
	public static boolean copyFile(String srcFileName, String destFileName) {
		return copyFile(srcFileName, destFileName, true);
	}

	/**
	 * 复制单个文件
	 * 
	 * @param srcFileName
	 *            待复制的文件名
	 * @param descFileName
	 *            目标文件名
	 * @param overlay
	 *            如果目标文件存在，是否覆盖
	 * @return 如果复制成功返回true，否则返回false
	 */
	public static boolean copyFile(String srcFileName, String destFileName, boolean overlay) {
		File srcFile = new File(srcFileName);

		// 判断源文件是否存在
		if (!srcFile.exists()) {
			log.info("源文件：" + srcFileName + "不存在！");
			return false;
		} else if (!srcFile.isFile()) {
			log.info("复制文件失败，源文件：" + srcFileName + "不是一个文件！");
			return false;
		}

		// 判断目标文件是否存在
		File destFile = new File(destFileName);
		if (destFile.exists()) {
			// 如果目标文件存在并允许覆盖
			if (overlay) {
				// 删除已经存在的目标文件，无论目标文件是目录还是单个文件
				new File(destFileName).delete();
			}
		} else {
			// 如果目标文件所在目录不存在，则创建目录
			if (!destFile.getParentFile().exists()) {
				// 目标文件所在目录不存在
				if (!destFile.getParentFile().mkdirs()) {
					// 复制文件失败：创建目标文件所在目录失败
					return false;
				}
			}
		}

		// 复制文件
		int byteread = 0; // 读取的字节数
		InputStream in = null;
		OutputStream out = null;

		try {
			in = new FileInputStream(srcFile);
			out = new FileOutputStream(destFile);
			byte[] buffer = new byte[1024];

			while ((byteread = in.read(buffer)) != -1) {
				out.write(buffer, 0, byteread);
			}
			return true;
		} catch (FileNotFoundException e) {
			return false;
		} catch (IOException e) {
			return false;
		} finally {
			try {
				if (out != null)
					out.close();
				if (in != null)
					in.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}

	/**
	 * 复制整个目录的内容(如果目标目录存在将覆盖)
	 * 
	 * @param srcDirName
	 *            待复制目录的目录名
	 * @param destDirName
	 *            目标目录名
	 * @return 如果复制成功返回true，否则返回false
	 */
	public static boolean copyDirectory(String srcDirName, String destDirName) {
		return copyDirectory(srcDirName, destDirName, true);
	}

	/**
	 * 复制整个目录的内容
	 * 
	 * @param srcDirName
	 *            待复制目录的目录名
	 * @param destDirName
	 *            目标目录名
	 * @param overlay
	 *            如果目标目录存在，是否覆盖
	 * @return 如果复制成功返回true，否则返回false
	 */
	public static boolean copyDirectory(String srcDirName, String destDirName, boolean overlay) {
		// 判断源目录是否存在
		File srcDir = new File(srcDirName);
		if (!srcDir.exists()) {
			log.info("复制目录失败：源目录" + srcDirName + "不存在！");
			return false;
		} else if (!srcDir.isDirectory()) {
			log.info("复制目录失败：" + srcDirName + "不是目录！");
			return false;
		}

		// 如果目标目录名不是以文件分隔符结尾，则加上文件分隔符
		if (!destDirName.endsWith(File.separator)) {
			destDirName = destDirName + File.separator;
		}
		File destDir = new File(destDirName);
		// 如果目标文件夹存在
		if (destDir.exists()) {
			// 如果允许覆盖则删除已存在的目标目录
			if (overlay) {
				new File(destDirName).delete();
			} else {
				log.info("复制目录失败：目的目录" + destDirName + "已存在！");
				return false;
			}
		} else {
			// 创建目的目录
			log.debug("目的目录不存在，准备创建。。。");
			if (!destDir.mkdirs()) {
				log.info("复制目录失败：创建目的目录失败！");
				return false;
			}
		}

		boolean flag = true;
		File[] files = srcDir.listFiles();
		for (int i = 0; i < files.length; i++) {
			// 复制文件
			if (files[i].isFile()) {
				flag = copyFile(files[i].getAbsolutePath(), destDirName + files[i].getName(), overlay);
				if (!flag)
					break;
			} else if (files[i].isDirectory()) {
				flag = copyDirectory(files[i].getAbsolutePath(), destDirName + files[i].getName(), overlay);
				if (!flag)
					break;
			}
		}
		if (!flag) {
			log.info("复制目录" + srcDirName + "至" + destDirName + "失败！");
			return false;
		} else {
			return true;
		}
	}

	/**
	 * 压缩文件或者文件夹
	 * 
	 * @param source
	 *            要压缩的文件或者文件夹 建议使用"c:/abc"或者"c:/abc/aaa.txt"这种形式来给定压缩路径
	 *            使用"c:\\abc"
	 *            或者"c:\\abc\\aaa.txt"这种形式来给定路径的话，可能导致出现压缩和解压缩路径意外故障。。。
	 * @param zipFileName
	 *            压缩后的zip文件名称 压缩后的目录组织与windows的zip压缩的目录组织相同。
	 *            会根据压缩的目录的名称，在压缩文件夹中创建一个改名的根目录， 其它压缩的文件和文件夹都在该目录下依照原来的文件目录组织形式
	 * @param encod
	 *            压缩文件的编码，默认采用gb2312
	 * @throws IOException
	 *             压缩文件的过程中可能会抛出IO异常，请自行处理该异常。
	 */
	public static void ZIP(String source, String zipFileName, String encod) throws IOException {
		ZipOutputStream zos = new ZipOutputStream(new File(zipFileName));

		// 设置压缩的时候文件名编码为gb2312
		if (encod != null) {
			zos.setEncoding(encod);
		} else {
			zos.setEncoding("gb2312");
		}

		File f = new File(source);

		if (f.isDirectory()) {
			// 如果直接压缩文件夹
			ZIPDIR(source, zos, f.getName() + "/");// 此处使用/来表示目录，如果使用\\来表示目录的话，会导致压缩后的文件目录组织形式在解压缩的时候不能正确识别。
		} else {
			// 如果直接压缩文件
			ZIPDIR(f.getPath(), zos, new File(f.getParent()).getName() + "/");
			ZIPFile(f.getPath(), zos, new File(f.getParent()).getName() + "/" + f.getName());
		}

		zos.closeEntry();
		zos.close();
	}

	/**
	 * zip 压缩单个文件。 除非有特殊需要，否则请调用ZIP方法来压缩文件！
	 * 
	 * @param sourceDir
	 *            需要压缩的目录位置
	 * @param zos
	 *            压缩到的zip文件
	 * @param tager
	 *            压缩到的目标位置
	 * @throws IOException
	 *             抛出文件异常
	 */
	public static void ZIPFile(String sourceFileName, ZipOutputStream zos, String tager) throws IOException {
		ZipEntry ze = new ZipEntry(tager);
		zos.putNextEntry(ze);

		// 读取要压缩文件并将其添加到压缩文件中
		FileInputStream fis = new FileInputStream(new File(sourceFileName));
		byte[] bf = new byte[2048];
		int location = 0;
		while ((location = fis.read(bf)) != -1) {
			zos.write(bf, 0, location);
		}
		fis.close();
	}

	/**
	 * 压缩目录。 除非有特殊需要，否则请调用ZIP方法来压缩文件！
	 * 
	 * @param sourceDir
	 *            需要压缩的目录位置
	 * @param zos
	 *            压缩到的zip文件
	 * @param tager
	 *            压缩到的目标位置
	 * @throws IOException
	 *             压缩文件的过程中可能会抛出IO异常，请自行处理该异常。
	 */
	public static void ZIPDIR(String sourceDir, ZipOutputStream zos, String tager) throws IOException {
		ZipEntry ze = new ZipEntry(tager);
		zos.putNextEntry(ze);
		// 提取要压缩的文件夹中的所有文件
		File f = new File(sourceDir);
		File[] flist = f.listFiles();
		if (flist != null) {
			// 如果该文件夹下有文件则提取所有的文件进行压缩
			for (File fsub : flist) {
				if (fsub.isDirectory()) {
					// 如果是目录则进行目录压缩
					ZIPDIR(fsub.getPath(), zos, tager + fsub.getName() + "/");
				} else {
					// 如果是文件，则进行文件压缩
					ZIPFile(fsub.getPath(), zos, tager + fsub.getName());
				}
			}
		}
	}

	/**
	 * 解压缩zip文件
	 * 
	 * @param sourceFileName
	 *            要解压缩的zip文件
	 * @param desDir
	 *            解压缩到的目录
	 * @throws IOException
	 *             压缩文件的过程中可能会抛出IO异常，请自行处理该异常。
	 */
	public static void UnZIP(String sourceFileName, String desDir) throws IOException {
		// 创建压缩文件对象
		ZipFile zf = new ZipFile(new File(sourceFileName));

		// 获取压缩文件中的文件枚举
		Enumeration<ZipEntry> en = zf.getEntries();
		int length = 0;
		byte[] b = new byte[2048];

		// 提取压缩文件夹中的所有压缩实例对象
		while (en.hasMoreElements()) {
			ZipEntry ze = en.nextElement();
			// 创建解压缩后的文件实例对象
			File f = new File(desDir + ze.getName());
			// 如果当前压缩文件中的实例对象是文件夹就在解压缩后的文件夹中创建该文件夹
			if (ze.isDirectory()) {
				f.mkdirs();
			} else {
				// 如果当前解压缩文件的父级文件夹没有创建的话，则创建好父级文件夹
				if (!f.getParentFile().exists()) {
					f.getParentFile().mkdirs();
				}

				// 将当前文件的内容写入解压后的文件夹中。
				OutputStream outputStream = new FileOutputStream(f);
				InputStream inputStream = zf.getInputStream(ze);
				while ((length = inputStream.read(b)) > 0)
					outputStream.write(b, 0, length);

				inputStream.close();
				outputStream.close();
			}
		}
		zf.close();
	}

	public static void main(String[] args) {
		try {
			ZIP("C:/Intel", "C:/cctv.zip", "gbk");
			UnZIP("C:/cctv.zip", "C:/cctv");
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

}
